package cn.demomaster.quicklogger

import android.Manifest
import android.content.Context
import android.content.pm.ApplicationInfo
import android.text.TextUtils
import android.util.Log
import cn.demomaster.quicklogger.config.LogConfig
import cn.demomaster.quicklogger.format.LogFormat
import cn.demomaster.quicklogger.interceptor.LogInterceptor
import cn.demomaster.quicklogger.model.LogInfo
import cn.demomaster.quicklogger.util.FileUtil
import cn.demomaster.quicklogger.util.PermissionUtil
import cn.demomaster.quicklogger.writer.FileWriter
import cn.demomaster.quicklogger.writer.LoggerWriter
import cn.demomaster.quicklogger.writer.MapBufferWriter
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone


class QdLog {

    companion object {
        const val DEFAULT_TAG = "ALog"

        private lateinit var mContext: Context
        private lateinit var mConfig: LogConfig
        private lateinit var logFormat: LogFormat
        private lateinit var loggerWriter: LoggerWriter

        private var _interceptors: ArrayList<LogInterceptor>? = null

        // 线程安全的日志缓存
        private var logStringBuffer = StringBuffer()
        var hasWritePermission: Boolean? = null

        // 权限常量
        public const val PERMISSION_WRITE_EXTERNAL = Manifest.permission.WRITE_EXTERNAL_STORAGE
        public const val PERMISSION_MANAGE_EXTERNAL = Manifest.permission.MANAGE_EXTERNAL_STORAGE

        @JvmField
        var simpleDateFormat: SimpleDateFormat =
            SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.CHINA)

        @JvmField
        var simpleDateFormat2: SimpleDateFormat =
            SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)// HH:mm:ss "yyyy年MM月dd日 HH:mm:ss:SSS"

        private var hasDealPermissionError: Boolean = false

        @JvmStatic
        fun init(context: Context, config: LogConfig) {
            mContext = context.applicationContext
            logFormat = LogFormat()
            mConfig = config
            mConfig.logFilePath //setLogPath(mConfig.logFileRelativePath);
            if (mConfig.writerMode == 0) { //mapbuffer写入
                loggerWriter = MapBufferWriter()
            } else { //普通文件写入
                loggerWriter = FileWriter()
            }
        }

        private fun doLog(logLevel: LogLevel, tag: String, obj: Any) {
            val logInfo = generateLogInfo(logLevel, tag, obj)
            applyInterceptors(logInfo)
            val logContent = formatAndPrintToAndroidLog(logInfo)
            if (shouldWriteToFile(logInfo)) {
                write(logInfo)
            }
        }

        private fun shouldWriteToFile(logInfo: LogInfo): Boolean {
            return mConfig.enable && logInfo.level.value() >= mConfig.logLevel
        }
        private fun formatAndPrintToAndroidLog(logInfo: LogInfo) {
            val header = logFormat.getHeaderStr(logInfo)
            when (logInfo.level) {
                LogLevel.VERBOSE -> Log.v(logInfo.tag, header)
                LogLevel.DEBUG   -> Log.d(logInfo.tag, header)
                LogLevel.INFO    -> Log.i(logInfo.tag, header)
                LogLevel.WARN    -> Log.w(logInfo.tag, header)
                LogLevel.ERROR   -> Log.e(logInfo.tag, header)
                LogLevel.PRINTLN -> kotlin.io.println(header)
                else             -> kotlin.io.println(header)
            }
        }

        private fun applyInterceptors(logInfo: LogInfo) {
            _interceptors?.forEach { it.onLog(logInfo) }
        }

        private fun generateLogInfo(logLevel: LogLevel, tag: String, obj: Any): LogInfo {
            val content = LogFormat.formatContent(obj)
            val threadId = Thread.currentThread().id

            if (!mConfig.showStackInfo) {
                return LogInfo(logLevel, tag, content, threadId, null, null, null, -1)
            }

            val stack = Thread.currentThread().stackTrace
            for (i in 2 until stack.size) {
                val element = stack[i]
                if (!element.className.startsWith(QdLog::class.qualifiedName.toString())) {
                    return LogInfo(
                        logLevel,
                        tag,
                        content,
                        threadId,
                        element.fileName,
                        element.className,
                        element.methodName,
                        element.lineNumber
                    )
                }
            }

            return LogInfo(logLevel, tag, content, threadId, null, null, null, -1)
        }

        var previousLogInfo: LogInfo? = null
        private fun write(logInfo: LogInfo) {
            // 检查并处理存储权限
            if (checkAndHandlePermissions()) return
            // 检查日志功能是否启用
            if (!mConfig.enable) return
            // 检查日志路径是否已配置
            if (TextUtils.isEmpty(mConfig.logFilePath)) {
                Log.e(mConfig.TAG, "未配置日志目录")
                return
            }
            // 格式化日志内容
            val logFilePath = logFileSavePath
            val (header, body) = logFormat.formatLogContent(mConfig,logInfo,previousLogInfo)
            previousLogInfo = logInfo
            val outputString = "$header$body"
            val formattedLog = logFormat.formatEnd(outputString)
            // 写入日志到文件
            if (mConfig.canWriteAble) {
                writeLogToFile(logFilePath, formattedLog)
            } else {
                //管理缓存
                manageLogBuffer(formattedLog)
            }
        }

        // 检查并处理存储权限
        private fun checkAndHandlePermissions(): Boolean {
            if (hasWritePermission == null) {
                hasWritePermission = PermissionUtil.checkWritePermission(mContext, QdLog.mConfig)
            }
            if (hasWritePermission == false) { //检查该存储权限
                logNoPermissionError()
                return true
            }
            return false
        }
        // 将日志写入文件
        private fun writeLogToFile(logFilePath: String, logContent: String) {
            val file = File(logFilePath)
            try {
                if (!file.exists()) {
                    FileUtil.createFile(file)
                    val logFileHeader = LogFormat.formatFileHeaderWithGraphic(mContext)
                    Log.d(mConfig.TAG, logFileHeader)
                    loggerWriter.writeLog(logFilePath, logFileHeader.toByteArray())
                }
                loggerWriter.writeLog(logFilePath, logContent.toByteArray())
            } catch (e: Exception) {
                Log.e(DEFAULT_TAG, "writeLog error: ${e.message}")
            }
        }

// 管理日志缓存
        private fun manageLogBuffer(logContent: String) {
            val currentSize = logStringBuffer!!.length
            val maxSize = mConfig.bufferMaxSize
            if (currentSize > maxSize) {
                val charsToRemove = currentSize - maxSize
                logStringBuffer!!.delete(0, charsToRemove)
            }
            logStringBuffer!!.append(logContent)
        }

        // ------------------ 对外 API ------------------

        @JvmStatic fun i(obj: Any) = doLog(LogLevel.INFO, mConfig.TAG, obj)
        @JvmStatic fun i(tag: String = DEFAULT_TAG, obj: Any) = doLog(LogLevel.INFO, tag, obj)
        @JvmStatic fun d(obj: Any) = doLog(LogLevel.DEBUG, mConfig.TAG, obj)
        @JvmStatic fun d(tag: String = DEFAULT_TAG, obj: Any) = doLog(LogLevel.DEBUG, tag, obj)
        @JvmStatic fun e(obj: Any) = doLog(LogLevel.ERROR, mConfig.TAG, obj)
        @JvmStatic fun e(tag: String = DEFAULT_TAG, obj: Any) = doLog(LogLevel.ERROR, tag, obj)
        @JvmStatic fun v(obj: Any) = doLog(LogLevel.VERBOSE, mConfig.TAG, obj)
        @JvmStatic fun v(tag: String = DEFAULT_TAG, obj: Any) = doLog(LogLevel.VERBOSE, tag, obj)
        @JvmStatic fun w(obj: Any) = doLog(LogLevel.WARN, mConfig.TAG, obj)
        @JvmStatic fun w(tag: String = DEFAULT_TAG, obj: Any) = doLog(LogLevel.WARN, tag, obj)
        @JvmStatic fun println(message: Any) = doLog(LogLevel.PRINTLN, mConfig.TAG, message)
        @JvmStatic fun println(tag: String = DEFAULT_TAG, message: Any) = doLog(LogLevel.PRINTLN, tag, message)

        @JvmStatic
        fun setEnable(enable: Boolean) {
            mConfig.enable = enable
        }

        /**
         * 设置时区
         * @param timeZone
         */
        @JvmStatic
        fun setTimeZone(timeZone: TimeZone?) {
            simpleDateFormat.timeZone = timeZone
            simpleDateFormat2.timeZone = timeZone
        }

        private val logFileSavePath: String
            get() {
                val logFileName = simpleDateFormat2.format(Date()) + mConfig!!.LOGFILE_SUFFIX
                return mConfig.logFilePath + logFileName
            }

        open fun getLogFilePathByDate(path: String): String {
            return mConfig.logFilePath + path
        }

        private fun logNoPermissionError() {
            if (!hasDealPermissionError) {
                hasDealPermissionError = true
                val err = "[log打印失败，请打开存储权限]"
                Log.e(mConfig.TAG, err)
            }
        }


        /**
         * 设置是否可以读写，一般在压缩文件时暂停写入。
         * @param mcanWriteAble
         */
        fun setCanWriteAble(mcanWriteAble: Boolean) {
            mConfig.canWriteAble = mcanWriteAble
            System.err.println((if (!mcanWriteAble) "暂停" else "恢复") + " 日志文件写入")
        }

        /**
         * 日志拦截器
         * @param logInterceptor
         */
        @JvmStatic
        fun addInterceptor(logInterceptor: LogInterceptor) {
            if (_interceptors == null) {
                _interceptors = ArrayList()
            }
            _interceptors!!.add(logInterceptor)
        }

        @JvmStatic fun removeInterceptor(interceptor: LogInterceptor?) {
            if (_interceptors == null) return
            if (interceptor == null) {
                _interceptors!!.clear()
            } else {
                _interceptors!!.remove(interceptor)
            }
        }

        val isDebug: Boolean
            get() {
                if (mContext == null) {
                    return true
                }
                return isDebug(mContext)
            }

        /**
         * 判断当前应用是否是debug状态
         */
        fun isDebug(context: Context?): Boolean {
            val isDebug = context!!.applicationInfo != null &&
                    (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
            return isDebug
        }
    }
}

